package d3d11.tutorials;

import java.awt.Component;
import java.util.concurrent.atomic.AtomicInteger;

import javax.swing.JFrame;

import org.bridj.jawt.JAWT;
import org.bridj.jawt.JAWTUtils;
import org.bridj.jawt.JAWTUtils.LockedComponentRunnable;
import org.bridj.jawt.JawtLibrary.JNIEnv;

import static org.bridj.BridJ.*;
import static org.bridj.Pointer.*;

import d3d11.D3D11.D3D_FEATURE_LEVEL;
import d3d11.core.D3D11_VIEWPORT;
import d3d11.core.ID3D10Blob;
import d3d11.core.ID3D11Device;
import d3d11.core.ID3D11DeviceContext;
import d3d11.resources.D3D11_BUFFER_DESC;
import d3d11.resources.D3D11_MAPPED_SUBRESOURCE;
import d3d11.resources.D3D11_SUBRESOURCE_DATA;
import d3d11.resources.ID3D11Buffer;
import d3d11.resources.ID3D11Texture2D;
import d3d11.resources.views.ID3D11RenderTargetView;
import d3d11.shader.ID3D11PixelShader;
import d3d11.shader.ID3D11VertexShader;
import d3d11.states.D3D11_INPUT_ELEMENT_DESC;
import d3d11.states.ID3D11InputLayout;
import static d3dx10.D3DX10.*;
import d3dx10.D3DXMATRIX;
import dxgi.DXGI;
import dxgi.IDXGIAdapter1;
import dxgi.IDXGIDevice1;
import dxgi.IDXGIFactory1;
import dxgi.IDXGISwapChain;

import static d3d11.D3D11.*;
import static d3d11.D3D11.D3D_DRIVER_TYPE.*;
import static d3d11.D3D11.D3D_FEATURE_LEVEL.*;
import static d3d11.D3D11.D3D11_USAGE.*;
import static d3d11.D3D11.D3D11_BIND_FLAG.*;
import static d3d11.D3D11.D3D11_MAP.*;
import static d3d11.D3D11.D3D11_MAP_FLAG.*;
import static d3d11.D3D11.D3D11_CPU_ACCESS_FLAG.*;
import static d3d11.D3D11.D3D11_INPUT_CLASSIFICATION.*;
import static d3d11.D3D11.D3D_PRIMITIVE_TOPOLOGY.*;

import static dxgi.DXGI.DXGI_FORMAT.*;

import static d3dcompiler.D3DCompiler.*;

public class Tutorial2Ex {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		// Create JFrame
		JFrame frame = new JFrame("D3D11 Tutorial 2");
		frame.setSize(800, 600);
		frame.setIgnoreRepaint(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
		
		// Create device
		final ID3D11Device device = D3D11CreateDevice(null, 
												D3D_DRIVER_TYPE_HARDWARE, 
												0, 
												new D3D_FEATURE_LEVEL[] { D3D_FEATURE_LEVEL_11_0 });
		final ID3D11DeviceContext immediateContext = device.GetImmediateContext();
		
		IDXGIDevice1 DXGIDevice = device.QueryInterface(IDXGIDevice1.class);
		IDXGIFactory1 DXGIFactory= DXGIDevice.GetParent(IDXGIAdapter1.class)
											 .GetParent(IDXGIFactory1.class);
		
		final IDXGISwapChain swapChain = DXGIFactory.CreateSwapChain(DXGIDevice, DXGI.SwapChainDescription(frame));
		DXGIDevice.Release();
		DXGIFactory.Release();
		
		ID3D11Texture2D backBuffer = swapChain.GetBuffer(0, ID3D11Texture2D.class);
		final ID3D11RenderTargetView rtView = device.CreateRenderTargetView(backBuffer, null);
		backBuffer.Release();

		immediateContext.OMSetRenderTargets(rtView, null);
		immediateContext.RSSetViewport(new D3D11_VIEWPORT(frame.getWidth(), frame.getHeight()));
		
		// Shader
		String shaders = 	"cbuffer perFrame : register( b0 )						\n" +
							"{														\n" +
							"	matrix world;										\n" +
							"}														\n" +
							"														\n" +
							"float4 VS( float4 Pos : POSITION ) : SV_POSITION		\n" +
							 "{														\n" +
							 "	return mul(Pos, world);					\n" +
							 "}														\n" +
							 "														\n" +
							 "float4 PS( float4 Pos : SV_POSITION ) : SV_Target		\n" +
							 "{														\n" +
							 "  return float4( 0.0f, 1.0f, 0.0f, 1.0f );			\n" +
							 "}";
		
		// Compiling for vertex shader and input layout
		ID3D10Blob code = D3DCompile(shaders, null, null, null, "VS", "vs_5_0", 0, 0);
		
		// Create input layout
		D3D11_INPUT_ELEMENT_DESC layoutDesc = new D3D11_INPUT_ELEMENT_DESC("POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0);
		final ID3D11InputLayout layout = device.CreateInputLayout(new D3D11_INPUT_ELEMENT_DESC[] { layoutDesc }, code);
		immediateContext.IASetInputLayout(layout);
		
		// Creating vertex shader
		final ID3D11VertexShader vs = device.CreateVertexShader(code, null);
		code.Release();
		
		// Creating pixel shader
		code = D3DCompile(shaders, null, null, null, "PS", "ps_5_0", 0, 0);
		final ID3D11PixelShader ps = device.CreatePixelShader(code, null);
		code.Release();
		
		// Create vertex buffer
		D3D11_BUFFER_DESC bufferDesc = new D3D11_BUFFER_DESC(D3D11_BIND_VERTEX_BUFFER, 
															 D3D11_USAGE_DEFAULT, 
															 D3D11_CPU_ACCESS_NONE, 
															 0,
															 (int)(9 * sizeOf(Float.class)));
		D3D11_SUBRESOURCE_DATA initData = new D3D11_SUBRESOURCE_DATA();
		initData.pSysMem(pointerToFloats(0.0f, 1.0f, 0.5f, 1f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f));
		initData.SysMemPitch(0).SysMemSlicePitch(0);
		final ID3D11Buffer vertexBuffer = device.CreateBuffer(bufferDesc, initData);
		delete(initData);	// Delete data of regain memory
		
		// Create constant buffer (size must be multiple of 16)
		bufferDesc.BindFlags(D3D11_BIND_CONSTANT_BUFFER);
		bufferDesc.Usage(D3D11_USAGE_DYNAMIC);
		bufferDesc.CPUAccessFlags(D3D11_CPU_ACCESS_WRITE);
		bufferDesc.ByteWidth((int) sizeOf(D3DXMATRIX.class));
		final ID3D11Buffer constBuffer = device.CreateBuffer(bufferDesc, null);
		
		final AtomicInteger angle = new AtomicInteger(0);
		final D3DXMATRIX mat = new D3DXMATRIX();
		D3DXMatrixIdentity(pointerTo(mat));
		
		JNIEnv env = JAWTUtils.getJNIEnv();
		JAWT jawt = JAWTUtils.getJAWT(env);
		while(frame.isEnabled()) {

			if(frame.isVisible())
				
				/*
				 * Drawing shall occur inside this methods, to prevent deadlock between
				 * Java drawing and native drawing
				 */
				JAWTUtils.withLockedSurface(env, jawt, frame, new LockedComponentRunnable() {
					
					@Override
					public void run(Component comp, long peer) {
						// Clear screen
						immediateContext.ClearRenderTargetView(pointerTo(rtView), pointerToFloats(0.0f, 0.125f, 0.3f, 1.0f));
						
						// Set vertex buffer
						int stride = (int) (sizeOf(Float.class) * 3);
						immediateContext.IASetVertexBuffers(0, 1, pointerToPointer(pointerTo(vertexBuffer)), pointerToInt(stride), pointerToInt(0));
						
						// Set shaders and input layout
						immediateContext.IASetInputLayout(layout);
						immediateContext.VSSetShader(pointerTo(vs), null, 0);
						immediateContext.PSSetShader(pointerTo(ps), null, 0);
						
						// Update rotation matrix for triangle
						D3DXMatrixRotationZ(pointerTo(mat), angle.addAndGet(1) * 0.01f);
						D3D11_MAPPED_SUBRESOURCE mappedData = immediateContext.Map(constBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0);
						pointerTo(mat).copyTo(mappedData.pData());
						immediateContext.Unmap(constBuffer, 0);
						immediateContext.VSSetConstantBuffers(0, 1, pointerToPointer(pointerTo(constBuffer)));
						
						// Set primitive topology and draw
						immediateContext.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
						immediateContext.Draw(3, 0);
						
						// Present to screen
						swapChain.Present(0, 0);
					}
				});
		
			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		vs.Release();
		ps.Release();
		layout.Release();
		vertexBuffer.Release();
		swapChain.Release();
		device.Release();
	}

}
